﻿using System.Collections.Generic;
using System.Linq.Expressions;
using Entropy.Ext.Ast;

namespace Entropy.Ext
{
	public class AsgBuilder : IAsgBuilder
	{
		private readonly IAccent accent;
		private readonly IComponentNaming naming;

		public AsgBuilder(IAccent accent, IComponentNaming naming)
		{
			this.accent = accent;
			this.naming = naming;
		}

		IEnumerable<Node> IAsgBuilder.Compile(Expression[] expressions)
		{
			var pronouncedNames = new Dictionary<Node, string>();
			var root = new Block
			{
				Children = SemanticParser.ParseExpressions(expressions, accent, pronouncedNames)
			};
			InstanciateConstructors(root, pronouncedNames);
			ProperlyAssignEvents(root);
			InverseNegativeBinaryOperators(root);
			CollapseDoubleNotOperators(root);
			ReplaceGetMethodWithPropertyAccess(root);

			return root.Children;
		}

		private static void CollapseDoubleNotOperators(Block root)
		{
			root.FindAndReplace<Unary>(
			                           u =>
			                           u.Operator == UnaryOperator.Not && u.Operand is Unary &&
			                           ((Unary) u.Operand).Operator == UnaryOperator.Not, u => ((Unary) u.Operand).Operand);
		}

		private static void ReplaceGetMethodWithPropertyAccess(Block root)
		{
			root.FindAndReplace<Call>(u => u.FunctionName.StartsWith("get_") && u.Arguments.Length == 0, u => new PropertyGet { Object = u.Object, PropName = u.FunctionName.Substring(4)});
		}

		private static void InverseNegativeBinaryOperators(Block root)
		{
			root.FindAndReplace<Unary>(u => u.Operator == UnaryOperator.Not && u.Operand is Binary, u =>
			{
				var b = (Binary) u.Operand;
				if (b.Operator == BinaryOperator.Equal)
				{
					b.Operator = BinaryOperator.NotEqual;
					return b;
				}
				if (b.Operator == BinaryOperator.NotEqual)
				{
					b.Operator = BinaryOperator.Equal;
					return b;
				}
				return u;
			});
		}

		private void ProperlyAssignEvents(Block root)
		{
			// replace  grid.beforeedit = f to grid.on('beforeedit', f) 
			root.FindAndReplace<Binary>(b => b.Operator == BinaryOperator.Assign && b.Right is FunctionDefinition, assign => accent.ResolveEvent(assign));
		}

		private void InstanciateConstructors(Block root, Dictionary<Node, string> pronouncedNames)
		{
			var varByCtor = new Dictionary<Call, Binary>();
			root.FindAndReplace<VariableDeclareOrRefer>(n => true, n => UniteAndNameConstructors(n, varByCtor, pronouncedNames));
		}

		private Node UniteAndNameConstructors(VariableDeclareOrRefer unresolved, Dictionary<Call, Binary> varByCtor, Dictionary<Node, string> pronouncedNames)
		{
			Binary instance;
			if (varByCtor.TryGetValue(unresolved.Ctor, out instance))
				return new Reference
				{
					Variable = (Variable) instance.Left
				};

			string name;
			if (!pronouncedNames.TryGetValue(unresolved.Ctor, out name))
				 name = naming.IssueVarName(unresolved.VarNameBasis);

			var result = new Binary
			{
				Left = new Variable
				{
					Name = name
				},
				Right = unresolved.Ctor,
				Operator = BinaryOperator.Assign
			};
			varByCtor.Add(unresolved.Ctor, result);
			return result;
		}
	}

}